package com.gitee.qdbp.general.common.web.controller.trade;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResponseMessage;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.annotation.OperateRecord;
import com.gitee.qdbp.base.enums.OperateType;
import com.gitee.qdbp.base.utils.WebTools;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayParams;
import com.gitee.qdbp.general.thirdpart.api.wechat.model.IWechatConfig;
import com.gitee.qdbp.general.thirdpart.api.wechat.model.WechatPrepayParams;
import com.gitee.qdbp.general.thirdpart.api.wechat.service.IWechatTradeService;
import com.gitee.qdbp.general.thirdpart.biz.PayUtils;
import com.gitee.qdbp.general.trade.enums.PayChannel;
import com.gitee.qdbp.tools.files.PathTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 微信支付流程<br>
 * <pre>
    <font color="red"><b>spring-web.xml</b></font>
    &lt;bean class="com.gitee.qdbp.general.system.web.controller.personnel.BaseWechatPaymentController" /&gt;
    <font color="red"><b>spring-biz.xml</b></font>
    &lt;bean class="com.gitee.qdbp.general.common.biz.thirdpart.wechat.WechatConfig"&gt;
        &lt;constructor-arg ref="setting" /&gt;
    &lt;/bean&gt;
    &lt;bean class="com.gitee.qdbp.general.common.biz.thirdpart.wechat.WechatTradeService" /&gt;
    &lt;bean class="com.gitee.qdbp.general.common.biz.service.XxxPaymentBusinessService" /&gt;
    <font color="red"><b>setting.properties</b></font>
    wechat.app_id = 
    wechat.app_secret = 
    wechat.encrypt_key = 
    wechat.mch_id = 
    # 如果NotifyUrl是本项目, 则前台传一个相对路径payment/wechat-notify.do
    # 如果NotifyUrl不是本项目, 则前台不传, 在setting.properties配置
    site.xxx.outer.url = http://ip:port/xxx/
    wechat.pay.merchant.notify_url = {config:site.xxx.outer.url}payment/wechat-notify.do
 *  </pre>
 *
 * @author zhaohuihua
 * @version 171023
 */
@RequestMapping()
public class BaseWechatPaymentController {

    protected Logger log = LoggerFactory.getLogger(this.getClass());

    protected String wxPrepayPage = "../views/payment/wechat-bridge";

    @Autowired
    protected IWechatConfig config;

    @Autowired
    private IWechatTradeService wechatTradeService;

    /** 微信支付参数 **/
    @ResponseBody
    @RequestMapping(value = "/payment/wechat-params")
    @OperateRecord(value = "微信支付:参数", type = OperateType.QUERY)
    public ResponseMessage params(PrepayParams params) {
        HttpServletRequest request = WebTools.me.getRequest();
        String currDomain = request == null ? null : request.getServerName();
        try {
            Map<String, Object> map = wechatTradeService.getPaymentParams(params);

            // 当前域名与网页授权域名不同时需要通过接口中转来获取网页授权
            String authDomain = config.getAuthorizeDomain();
            if (VerifyTools.isNoneBlank(authDomain, currDomain) && VerifyTools.notEquals(authDomain, currDomain)) {
                map.put("authUrl", config.getAuthorizeProxyUrl());
            }

            return new ResponseMessage(map);
        } catch (ServiceException e) {
            return new ResponseMessage(e);
        }
    }

    /** 发起预支付 **/
    @ResponseBody
    @RequestMapping(value = "/payment/wechat-prepay.json")
    @OperateRecord(value = "微信支付:发起", type = OperateType.CREATE)
    public ResponseMessage prepayByAjax(WechatPrepayParams params) {

        handlePrepayParams(params);
        params.setIpAddress(WebTools.me.getIpAddress());

        try {
            Map<String, Object> result = wechatTradeService.createPrepay(params);
            return new ResponseMessage(result);
        } catch (ServiceException e) {
            return new ResponseMessage(e);
        }
    }

    /** 发起预支付(仅用于微信公众号支付, 网页授权的回调页面, 必须有code, 通过code换取网页授权access_token) **/
    @RequestMapping(value = "/payment/wechat-prepay")
    @OperateRecord(value = "微信支付:发起", type = OperateType.CREATE)
    public ModelAndView prepayByHtml(WechatPrepayParams params, HttpServletResponse response) throws ServiceException {
        if (VerifyTools.isBlank(params.getCode())) {
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        handlePrepayParams(params);
        params.setIpAddress(WebTools.me.getIpAddress());

        Map<String, Object> result = wechatTradeService.createPrepay(params);
        return new ModelAndView(wxPrepayPage, "pv", result);
    }

    // 网页授权域名: 微信公众平台(mp.weixin.qq.com) -- 设置 -- 公众号设置 -- 功能设置 -- 网页授权域名
    // 当前域名与网页授权域名不同时需要通过此接口中转来获取网页授权, 否则会报redirect_uri域名与后台配置不一致
    @RequestMapping(value = "/payment/wechat-authorize")
    @OperateRecord(value = "微信支付:跳转", type = OperateType.QUERY)
    public ModelAndView authProxy() throws ServiceException {
        Map<String, String> map = WebTools.me.getRequestParams();
        String code = map.get("code");
        String returnUrl = map.get("return_url");

        String url;
        if (VerifyTools.isNoneBlank(code, returnUrl)) { // 返回
            map.remove("return_url");
            String params = PayUtils.mapToParams(map);
            url = returnUrl + (returnUrl.indexOf("?") < 0 ? "?" : "&") + params;
        } else { // 发送
            // https://open.weixin.qq.com/connect/oauth2/authorize
            // ?appid=...&response_type=code&scope=snsapi_base&state=wechat-pay&redirect_uri=...#wechat_redirect
            String redirectUri = map.remove("redirect_uri");
            if (redirectUri != null) {
                try {
                    redirectUri = URLEncoder.encode(redirectUri, "UTF-8");
                } catch (Exception e) {
                }
            }
            String proxyUrl = config.getAuthorizeProxyUrl();
            if (VerifyTools.isBlank(proxyUrl)) {
                log.error("AuthorizeProxyUrl is required.");
                throw new ServiceException(ResultCode.SERVER_INNER_ERROR);
            }
            String nextUrl = proxyUrl + (proxyUrl.indexOf("?") < 0 ? "?" : "&") + "return_url=" + redirectUri;
            try {
                nextUrl = URLEncoder.encode(nextUrl, "UTF-8");
            } catch (Exception e) {
            }
            map.put("redirect_uri", nextUrl);
            String params = PayUtils.mapToParams(map);
            String apiUrl = config.getAuthorizeApiUrl();
            url = apiUrl + (apiUrl.indexOf("?") < 0 ? "?" : "&") + params + "#wechat_redirect";
        }
        return new ModelAndView("redirect:" + url);
    }

    protected void handlePrepayParams(WechatPrepayParams params) {
        params.setPayChannel(PayChannel.WECHAT);
        if (VerifyTools.isNotBlank(params.getReturnUrl()) && !StringTools.isUrl(params.getReturnUrl())) {
            params.setReturnUrl(getWebUrl(params.getReturnUrl()));
        }
        if (VerifyTools.isNotBlank(params.getNotifyUrl()) && !StringTools.isUrl(params.getNotifyUrl())) {
            params.setNotifyUrl(getWebUrl(params.getNotifyUrl()));
        }
    }

    /** 支付回调通知 **/
    @RequestMapping("/payment/wechat-notify")
    @OperateRecord(value = "微信支付:回调", type = OperateType.UPDATE)
    public void notify(HttpServletRequest request, HttpServletResponse response) {

        String xml;
        try {
            // 获取微信POST过来的反馈信息
            xml = readRequestContentForXml(request);
        } catch (IOException e) {
            log.error("[WechatPay]NotifyRequestReadError.\n\t{}", request.getRequestURL(), e);
            return;
        }

        String text = wechatTradeService.notifyHandle(xml);

        try {
            response.getWriter().write(text);
        } catch (IOException e) {
            String m = "[WechatPay]NotifyResponseWriteError.\n\t{}\n\t{}";
            log.error(m, text, JsonTools.toJsonString(xml), e);
        }
    }

    protected String getWebUrl(String uri) {
        if (uri == null || StringTools.isUrl(uri)) {
            return uri;
        }

        String prefix = config.getPaymentUrlPrefix();
        if (VerifyTools.isBlank(prefix)) {
            return WebTools.me.getWebUrl(uri);
        } else {
            if (uri.startsWith("/")) {
                // prefix和uri都带工程名
                String ctxPath = WebTools.me.getContextPath();
                if (VerifyTools.isNotBlank(ctxPath) && uri.startsWith(ctxPath)) {
                    uri = uri.substring(ctxPath.length());
                }
                return PathTools.concat(prefix, uri);
            } else {
                return PathTools.concat(prefix, uri);
            }
        }
    }

    protected String readRequestContentForXml(HttpServletRequest request) throws IOException {

        try (InputStream is = request.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "UTF-8");
                BufferedReader reader = new BufferedReader(isr)) {

            StringBuilder xml = new StringBuilder();

            String text;
            while ((text = reader.readLine()) != null) {
                xml.append(text);
            }
            return xml.toString();
        }
    }
}
